home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1998 November: Tool Chest / Dev.CD Nov 98 TC.toast / Sample Code / Networking / OT Server Sample / OTVirtualClient / OTVirtualClient.c next >
Encoding:
Text File  |  1996-12-19  |  41.0 KB  |  1,648 lines  |  [TEXT/CWIE]

  1. //
  2. //        OTVirtualClient             by Eric Okholm            Version 1.0
  3. //
  4. //        This is an OpenTransport sample client application which can be used 
  5. //        to exercise and test the OpenTransport Virtual Server sample.
  6. //        It also demonstrates coding techniques for OT client applications.
  7. //
  8. //        You are welcome to use this code in any way to create you own
  9. //        OpenTransport applications.   For more information on this program,
  10. //        please review the document "About OTVirtual Server".
  11. //
  12. //        What's new in version 1.0.1:
  13. //
  14. //            - No changes, it just kept the version number parallel
  15. //              to OTVirtualServer version 1.0.1.
  16. //
  17. //        Go Bears, beat Stanford !!!
  18. //
  19.  
  20. #define DoAlert(x)            { sprintf(gProgramErr, x); gProgramState = kProgramError; }        
  21. #define DoAlert1(x, y)        { sprintf(gProgramErr, x, y); gProgramState = kProgramError; }        
  22. #define DoAlert2(x, y, z)    { sprintf(gProgramErr, x, y, z); gProgramState = kProgramError;}    
  23.  
  24. //
  25. //    Program mode
  26. //
  27. //    Before compiling, 
  28. //    set kDebugLevel to 0 for production
  29. //                    or 1 for debug code.
  30. //
  31. //    In production mode, the code attempts to recover cleanly from any problems in encounters.
  32. //    In debug mode, the unexplained phenomenon cause an alert box highlighting the situation
  33. //    to be delivered and then the program exits.
  34. //
  35.  
  36. #define kDebugLevel    1
  37.  
  38. #if kDebugLevel > 0
  39.  
  40. #define DBAlert(x)            DoAlert(x)        
  41. #define DBAlert1(x, y)        DoAlert1(x, y)        
  42. #define DBAlert2(x, y, z)    DoAlert2(x, y, z)    
  43.  
  44. #else
  45.  
  46. #define DBAlert(x)            { }
  47. #define DBAlert1(x, y)        { }
  48. #define DBAlert2(x, y, z)    { }
  49.  
  50. #endif
  51.  
  52. //
  53. //    Include files
  54. //
  55. #include <Dialogs.h>
  56. #include <Events.h>
  57. #include <Fonts.h>
  58. #include <GestaltEqu.h>
  59. #include <Memory.h>
  60. #include <Menus.h>
  61. #include <QuickDraw.h>
  62. #include <SegLoad.h>
  63. #include <stdio.h>
  64. #include <StdLib.h>
  65. #include <String.h>
  66. #include <strings.h>
  67. #include <ToolUtils.h>
  68. #include <Windows.h>
  69.  
  70. #include <OpenTptInternet.h>        // includes OpenTransport.h
  71. #include <OpenTptClient.h>            // needed for OTReleaseBuffer()
  72.  
  73. //
  74. //    Defines, enums, resource IDs
  75. //
  76. #define kInFront                    (WindowPtr) -1
  77. #define    kWindowResID                128
  78.  
  79.     // Apple Menu
  80. #define kAppleMenuResID                128
  81. #define    kAppleMenuAbout                1
  82.  
  83.     // File Menu
  84. #define kFileMenuResID                129
  85. #define kFileMenuOpen                1
  86. #define    kFileMenuClose                2
  87. #define    kFileMenuQuit                4
  88.  
  89.     // Edit Menu
  90. #define kEditMenuResID                130                // Edit menu is disabled
  91.  
  92.     // Client Menu
  93. #define kClientMenuResID            131
  94. #define kClientMenuTCPPrefs            1
  95.  
  96.     // Alerts, etc.
  97. #define kAlertExitResID                128
  98. #define kAboutBoxResID                130
  99.  
  100.     // TCP Prefs Dialog
  101. #define kTCPPrefsDlogResID            129
  102. #define    kServerAddrDItem            2
  103. #define kServerPortDItem            4
  104. #define    kMaxConnectionsDItem        6
  105. #define kStartStopDItem                7
  106.  
  107.     // Overall program states
  108. enum
  109. {
  110.     kProgramRunning        = 0,
  111.     kProgramDone        = 1,
  112.     kProgramError        = 2
  113. };
  114.  
  115.     // Server states
  116. enum
  117. {
  118.     kClientStopped        = 0,
  119.     kClientRunning        = 1,
  120.     kClientShuttingDown    = 2
  121. };
  122.  
  123.     // Bit numbers in EPInfo stateFlags fields
  124. enum
  125. {
  126.     kOpenInProgressBit                = 0
  127. };
  128.  
  129.     // Misc stuff
  130. enum
  131. {
  132.     kTimerIntervalInSeconds        = 3,
  133.     kTimerInterval                = (kTimerIntervalInSeconds * 1000),
  134.     kServerRequestSize            = 128,
  135.     kOTVersion111                = 0x01110000
  136. };
  137.  
  138.     // Endpoint Info Structure
  139. struct EPInfo
  140. {
  141.     EndpointRef        erf;                //    actual endpoint
  142.     EPInfo*            next;                //    used to link all acceptor's EPInfos (not atomic)
  143.     OTLink            link;                //    link into an OT LIFO (atomic)
  144.     UInt8            stateFlags;            //    various status fields
  145. };
  146. typedef struct EPInfo EPInfo;
  147.  
  148. //
  149. //    Globals
  150. //
  151. EPInfo*                gDNS                        = NULL;
  152. EPInfo*             gConnectors                    = NULL;
  153. InetHostInfo        gServerHostInfo;
  154. Boolean                gWaitForServerAddr;
  155. int                    gClientState                = kClientStopped;
  156. int                    gProgramState                = kProgramRunning;
  157. char                gProgramErr[128];
  158. DialogPtr            gDialogPtr                    = NULL;
  159. WindowPtr            gWindowPtr                    = NULL;
  160. long                gSleepTicks                    = 60;
  161. Str255                gServerAddrStr                = "\p17.202.32.195";
  162. long                gServerAddr                    = 0;
  163. Str255                gServerPortStr                = "\p2001";
  164. long                gServerPort                    = 0;
  165. Str255                gMaxConnectionsStr            = "\p100";
  166. long                gMaxConnections                = 0;
  167. Boolean                gClientRunning                = false;
  168. Str255                gStartStr                    = "\pStart";
  169. Str255                gStopStr                    = "\pStop";
  170. SInt32                gCntrEndpts                    = 0;
  171. SInt32                gCntrIdleEPs                = 0;
  172. SInt32                gCntrBrokenEPs                = 0;
  173. SInt32                gCntrPending                = 0;
  174. SInt32                gCntrConnections            = 0;
  175. SInt32                gCntrTotalConnections        = 0;
  176. SInt32                gCntrBytesRcvd                = 0;
  177. SInt32                gCntrTotalBytesRcvd            = 0;
  178. SInt32                gCntrDiscon                    = 0;
  179. OTLIFO                gIdleEPLIFO;
  180. OTLIFO*                gIdleEPs                    = &gIdleEPLIFO;
  181. OTLIFO                gBrokenEPLIFO;
  182. OTLIFO*                gBrokenEPs                    = &gBrokenEPLIFO;
  183. OTConfiguration*    gCfgMaster                    = NULL;
  184. Boolean                gWaitForEventLoop            = false;
  185. long                gTimerTask                    = 0;
  186. SInt32                gCntrIntervalConnects        = 0;
  187. SInt32                gCntrIntervalBytes            = 0;
  188. SInt32                gConnectsPerSecond            = 0;
  189. SInt32                gConnectsPerSecondMax        = 0;
  190. SInt32                gKBytesPerSecond            = 0;
  191. SInt32                gKBytesPerSecondMax            = 0;
  192. SInt32                gCntrIntervalEventLoop        = 0;
  193. SInt32                gEventsPerSecond            = 1;
  194. SInt32                gEventsPerSecondMax            = 1;
  195. Boolean                gDoWindowUpdate                = true;
  196. unsigned char         gServerRequest[kServerRequestSize];
  197. OSType                gOTVersionSelector            = 'otvr';
  198. UInt32                gOTVersion;
  199.  
  200. //
  201. //    OpenTransport Networking Code Prototypes
  202. //
  203. static void            DoConnect(EPInfo*);
  204. static Boolean         EPClose(EPInfo*);
  205. static Boolean        EPOpen(EPInfo*);
  206. static void            NetEventLoop(void);
  207. static void            NetInit(void);
  208. static void            NetShutdown(void);
  209. static pascal void    Notifier(void*, OTEventCode, OTResult, void*);
  210. static void            ReadData(EPInfo*);
  211. static void            Recycle(void);
  212. static void         SendRequest(EPInfo*);
  213. static void         StartClient(void);
  214. static void         StopClient(void);
  215. static void            TimerInit();
  216. static void            TimerDestroy();
  217. static pascal void    TimerRun(void*);
  218.  
  219. //
  220. //    Macintosh Program Wrapper Prototypes 
  221. //
  222. static void            AboutBox(void);
  223. static void            AlertExit(Str255);
  224. static void            DialogClose(void);
  225. static Boolean         EventDialog(EventRecord*);
  226. static void            EventDrag(WindowPtr, Point);
  227. static void            EventGoAway(WindowPtr, Point);
  228. static void            EventKeyDown(EventRecord*);
  229. static void            EventLoop(void);
  230. static void            EventMouseDown(EventRecord*);
  231. static void         MacInit(void);
  232. static void         MacInitROM(void);
  233. static void            C2PStr(char*, Str255);
  234. static void            P2CStr(Str255, char*);
  235. static void         MenuDispatch(long);
  236. static void         SetupMenus(void);
  237. static void         TCPPrefsDialog(void);
  238. static void            TCPPrefsReset(void);
  239. static void         WindowClose(void);
  240. static void         WindowOpen(void);
  241. static void            WindowUpdate(void);
  242.  
  243.  
  244. //////////////////////////////////////////////////////////////////////////////////////
  245. //
  246. //    OpenTransport Networking Code
  247. //
  248. //    The code in this section provides the networking portions of the 
  249. //    OpenTransport Virtual Client.
  250. //
  251. //////////////////////////////////////////////////////////////////////////////////////
  252.  
  253. //
  254. //    DoBind
  255. //
  256. //    This routine requests a wildcard port binding from the transport protocol.
  257. //    Since the program doesn't care what port is returned, it passes in NULL
  258. //    for the bind return parameter.  The bind request structure is ephemeral
  259. //    and can be a local stack variable since OT is done with it when the call returns.
  260. //    The bind is done when the notifier receives a T_BINDCOMPLETE event.
  261. //
  262. static void DoBind(EPInfo* epi)
  263. {
  264.     OSStatus err;
  265.     TBind bindReq;
  266.     InetAddress    inAddr;
  267.     
  268.     //
  269.     //    Bind the endpoint to a wildcard address 
  270.     //    (assign us a port, we don't care which one).
  271.     //
  272.     OTInitInetAddress(&inAddr, 0, 0);
  273.     bindReq.addr.len = sizeof(InetAddress);
  274.     bindReq.addr.buf = (unsigned char*) &inAddr;
  275.     bindReq.qlen = 0;
  276.     
  277.     err = OTBind(epi->erf, &bindReq, NULL);
  278.     if (err != kOTNoError)
  279.     {
  280.         DBAlert1("DoBind: OTBind error %d", err);
  281.         return;
  282.     }
  283. }
  284.  
  285. //
  286. //    DoConnect
  287. //
  288. //    This routine attempts establish a new connection to the globally known
  289. //    server address and port.   If the program is still trying to use the
  290. //    DNR to resolve the server's host name into an IP address, the endpoint
  291. //    is queued for later connection.   
  292. //
  293. static void DoConnect(EPInfo* epi)
  294. {
  295.     OSStatus err;
  296.     TCall sndCall;
  297.     InetAddress inAddr;
  298.     OTLink* link;
  299.     
  300.     //    Don't want new connections if already shutting down.
  301.     if (gProgramState != kProgramRunning || gClientState != kClientRunning)
  302.         return;
  303.         
  304.     if (gWaitForServerAddr || gWaitForEventLoop)
  305.     {
  306.         if (epi != NULL)
  307.         {
  308.             OTLIFOEnqueue(gIdleEPs, &epi->link);
  309.             OTAtomicAdd32(1, &gCntrIdleEPs);
  310.         }
  311.         return;
  312.     }
  313.         
  314.     //    If we weren't passed a specific EPInfo, try to get an idle one.
  315.     if (epi == NULL)
  316.     {
  317.         link = OTLIFODequeue(gIdleEPs);
  318.         if (link == NULL)
  319.             return;
  320.         OTAtomicAdd32(-1, &gCntrIdleEPs);
  321.         epi = OTGetLinkObject(link, EPInfo, link);
  322.     }
  323.  
  324.     OTInitInetAddress(&inAddr, gServerPort, gServerAddr);
  325.     OTMemzero(&sndCall, sizeof(TCall));
  326.     sndCall.addr.len     = sizeof(InetAddress);                
  327.     sndCall.addr.buf    = (unsigned char*) &inAddr;
  328.     
  329.     OTAtomicAdd32(1, &gCntrPending);
  330.     err = OTConnect(epi->erf, &sndCall, NULL);
  331.     if (err != kOTNoDataErr)
  332.     {
  333.         OTAtomicAdd32(-1, &gCntrPending);
  334.         DBAlert2("DoConnect: OTConnect error %d state %d", err, OTGetEndpointState(epi->erf));
  335.         return;
  336.     }
  337.     
  338.     //
  339.     //    If OTConnect didn't return an error, this thread will 
  340.     //    resume when the notifier gets a T_CONNECT event...
  341.     //
  342. }
  343.  
  344. //
  345. //    EPClose
  346. //
  347. //    This routine is a front end to OTCloseProvider.   
  348. //    Centralizing closing of endpoints makes debugging and instrumentation easier.  
  349. //
  350. static Boolean EPClose(EPInfo* epi)
  351. {
  352.     OSStatus err;
  353.     
  354.     //
  355.     //    If an endpoint is still being opened, we can't close it yet.
  356.     //    There is no way to cancel an OTAsyncOpenEndpoint, so we just
  357.     //    have to wait for the T_OPENCOMPLETE event at the notifier.
  358.     //
  359.     if ( OTAtomicTestBit(&epi->stateFlags, kOpenInProgressBit) )
  360.         return false;
  361.         
  362.     err = OTCloseProvider(epi->erf);
  363.     epi->erf = NULL;
  364.     if (err)
  365.         DBAlert1("EPClose: OTCloseProvider error %d", err);
  366.  
  367.     if (epi != gDNS)
  368.         OTAtomicAdd32(-1, &gCntrEndpts);
  369.     return true;
  370.     
  371. }
  372.  
  373. //
  374. //    EPOpen:
  375. //
  376. //    A front end to OTAsyncOpenEndpoint.
  377. //    A status bit is set so we know there is an open in progress.
  378. //    It is cleared when the notifier gets a T_OPENCOMPLETE where the context
  379. //    pointer is this EPInfo.  Until that happens, this EPInfo can't be cleaned
  380. //    up and released.
  381. //
  382. static Boolean EPOpen(EPInfo* epi, OTConfiguration* cfg)
  383. {
  384.     OSStatus err;
  385.     
  386.     OTAtomicSetBit(&epi->stateFlags, kOpenInProgressBit);
  387.     err = OTAsyncOpenEndpoint(cfg, 0, NULL, &Notifier, epi);
  388.     if (err != kOTNoError)
  389.     {
  390.         OTAtomicClearBit(&epi->stateFlags, kOpenInProgressBit);
  391.         DBAlert1("EPOpen: OTAsyncOpenEndpoint error %d", err);
  392.         return false;
  393.     }
  394.     return true;
  395. }
  396.  
  397. //
  398. //    NetEventLoop
  399. //
  400. //    This routine is called once during each pass through the program's event loop.
  401. //    If the program is running on OT 1.1.2 or an earlier release, this is where
  402. //    outbound orderly releases are started (see comments in DoSndOrderlyRelease 
  403. //    for more information on that).   This is also where endpoints are "fixed" by
  404. //    closing them and opening a new one to replace them.   This is rarely necessary,
  405. //    but works around some timing issues in OTUnbind().  Having passed through the 
  406. //    event loop once, we assume it is safe to turn off throttle-back.  And, finally,
  407. //    if we have deferred handing of a T_LISTEN, here we start it up again.
  408. //
  409. static void NetEventLoop()
  410. {
  411.     Recycle();
  412.     gWaitForEventLoop = false;
  413.     DoConnect(NULL);
  414. }
  415.  
  416. //
  417. //    NetInit:
  418. //
  419. //    This is nothing but a front end to InitOpenTransport.
  420. //    The only reason for having this routine is to get the call to InitOpenTransport
  421. //    up into the "networking" section of the program and out of the 
  422. //    "macintosh program wrapper" section of the program.
  423. //
  424. static void NetInit()
  425. {
  426.     OSStatus err;
  427.     
  428.     err = InitOpenTransport();
  429.     if (err)
  430.     {
  431.         DBAlert1("NetInit: InitOpenTransport error %d", err);
  432.         return;
  433.     }
  434.     err = Gestalt(gOTVersionSelector, (long*) &gOTVersion);
  435.     if (err || (gOTVersion < kOTVersion111))
  436.     {
  437.         DoAlert("Please install Open Transport 1.1.1 or later");
  438.         return;
  439.     }
  440.     TimerInit();
  441. }
  442.  
  443. //
  444. //    NetShutdown:
  445. //
  446. //    Ditto...
  447. //
  448. static void NetShutdown()
  449. {
  450.     TimerDestroy();
  451.     CloseOpenTransport();
  452. }    
  453.  
  454.  
  455. //
  456. //    Notifier:
  457. //
  458. //    Most of the interesting networking code in this program resides inside 
  459. //    this notifier.   In order to run asynchronously and as fast as possible,
  460. //    things are done inside the notifier whenever possible.  Since almost
  461. //    everything is done inside the notifier, there was little need for specical
  462. //    synchronization code.
  463. //
  464. //    Note: The DNR events are combined with normal endpoint events in this notifier.
  465. //    The only events which are expected from the DNR are T_DNRSTRINGTOADDRCOMPLETE
  466. //    and T_OPENCOMPLETE.
  467. //
  468. //    IMPORTANT NOTE:  Normal events defined by XTI (T_LISTEN, T_CONNECT, etc)
  469. //    and OT completion events (T_OPENCOMPLETE, T_BINDCOMPLETE, etc.) are not
  470. //    reentrant.  That is, whenever our notifier is invoked with such an event,
  471. //    the notifier will not be called again by OT for another normal or completion
  472. //    event until we have returned out of the notifier - even if we make OT calls
  473. //    from inside the notifier.   This is a useful synchronization tool.
  474. //    However, there are two kinds of events which will cause the notifier to 
  475. //    be reentered.   One is T_MEMORYRELEASED, which always happens instantly.
  476. //    The other are state change events like kOTProviderWillClose.
  477. //
  478.  
  479. static pascal void Notifier(void* context, OTEventCode event, OTResult result, void* cookie)
  480. {
  481.     OSStatus err;
  482.     OTResult epState;
  483.     EPInfo* epi = (EPInfo*) context;
  484.  
  485.     //
  486.     //    Once the program is shutting down, most events would be uninteresting.
  487.     //    However, we still need T_OPENCOMPLETE and T_MEMORYRELEASED events since
  488.     //    we can't call CloseOpenTransport until all OTAsyncOpenEndpoints and
  489.     //    OTSends with AckSends have completed.   So those specific events
  490.     //    are still accepted.
  491.     //
  492.     if (gProgramState != kProgramRunning)
  493.     {
  494.         if (event != T_OPENCOMPLETE)
  495.             return;
  496.     }
  497.     
  498.     //
  499.     //    This really isn't necessary, it's just a sanity check which should be removed
  500.     //    once a program is debugged.   It's just making sure we don't get event notifications
  501.     //    after all of our endpoints have been closed.
  502.     //
  503.     if (gClientState == kClientStopped)
  504.     {
  505.         DBAlert1("Notifier: got event %d when client not running!", event);
  506.         return;
  507.     }
  508.     
  509.     //
  510.     //    Within the notifier, all action is based on the event code.
  511.     //    In this notifier, fatal errors all break out of the switch to the bottom.
  512.     //    As long as everything goes as expected, the case returns rather than breaks.
  513.     //
  514.     switch (event)
  515.     {
  516.         //
  517.         //    T_BINDCOMPLETE:
  518.         //
  519.         //    This event is returned when an endpoint has been bound to a wildcard addr.
  520.         //    No errors are expected.   The program immediately attempts to establish
  521.         //    a connection from this endpoint to the server.
  522.         //
  523.         case T_BINDCOMPLETE:
  524.         {
  525.             if (result != kOTNoError)
  526.             {
  527.                 DBAlert1("Notifier: T_BINDCOMPLETE result %d", result);
  528.                 return;
  529.             }
  530.             DoConnect(epi);            // resumes at T_CONNECT
  531.             return;
  532.         }
  533.         
  534.         //
  535.         //    T_CONNECT:
  536.         //
  537.         //    This event is returned when a connection is established to the server.
  538.         //    The program must call OTRcvConnect() to get the conenction information
  539.         //    and clear the T_CONNECT event from the stream.  Since OTRcvConnect()
  540.         //    returns immediately (rather than via a completion event to the notifier)
  541.         //    we can use local stack structures for parameters.
  542.         //
  543.         case T_CONNECT:
  544.         {
  545.             TCall call;
  546.             InetAddress caddr;
  547.             
  548.             if (result != kOTNoError)
  549.             {
  550.                 DBAlert1("Notifier: T_CONNECT result %d", result);
  551.                 return;
  552.             }
  553.             
  554.             call.addr.maxlen = sizeof(InetAddress);
  555.             call.addr.buf = (unsigned char*) &caddr;
  556.             call.opt.maxlen = 0;
  557.             call.opt.buf = NULL;
  558.             call.udata.maxlen = 0;
  559.             call.udata.buf = NULL;
  560.             
  561.             err = OTRcvConnect(epi->erf, &call);
  562.             if (err != kOTNoError)
  563.             {
  564.                 DBAlert1("Notifier: T_CONNECT - OTRcvConnect err %d", err);
  565.                 return;
  566.             }
  567.             OTAtomicAdd32(-1, &gCntrPending);
  568.             OTAtomicAdd32(1, &gCntrConnections);
  569.             OTAtomicAdd32(1, &gCntrTotalConnections);
  570.             OTAtomicAdd32(1, &gCntrIntervalConnects);
  571.             
  572.             SendRequest(epi);
  573.  
  574.             //
  575.             //    Since we won't be sending any data, 
  576.             //    might as well send along the orderly release now.
  577.             //
  578.             err = OTSndOrderlyDisconnect(epi->erf);
  579.             if (err != kOTNoError)
  580.             {
  581.                 if (err != kOTLookErr)
  582.                 {
  583.                     DBAlert1("Notifier: T_CONNECT: OTSndOrderlyDisconnect error %d", err);
  584.                 }
  585.             }
  586.             return;                // Wait for a T_DATA...
  587.         }
  588.         
  589.         //
  590.         //    T_DATA:
  591.         //
  592.         //    The main rule for processing T_DATA's is to remember that once you have
  593.         //    a T_DATA, you won't get another one until you have read to a kOTNoDataErr.
  594.         //    The advanced rule is to remember that you could get another T_DATA
  595.         //    during an OTRcv() which will eventually return kOTNoDataErr, presenting
  596.         //    the application with a synchronization issue to be most careful about.
  597.         //    
  598.         //    In this application, since an OTRcv() calls are made from inside the notifier,
  599.         //    this particular synchronization issue doesn't become a problem.
  600.         //
  601.         case T_DATA:
  602.         {
  603.             ReadData(epi);
  604.             return;
  605.         }
  606.         
  607.         //
  608.         //    T_DNRSTRINGTOADDRCOMPLETE:
  609.         //
  610.         //    This event occurs when the DNR has finished an attempt to translate
  611.         //    the server's name into an IP address we can use to connect to.
  612.         //
  613.         case T_DNRSTRINGTOADDRCOMPLETE:
  614.         {
  615.             if (result != kOTNoError)
  616.             {
  617.                 DBAlert1("Notifier: T_DNRSTRINGTOADDRCOMPLETE result %d", result);
  618.                 return;
  619.             }
  620.             gServerAddr = gServerHostInfo.addrs[0];
  621.             gWaitForServerAddr = false;
  622.             return;
  623.         }
  624.                 
  625.         //
  626.         //    T_DISCONNECT:
  627.         //
  628.         //    An inbound T_DISCONNECT event usually indicates that the other side of the
  629.         //    connection did an abortive disconnect (as opposed to an orderly release).
  630.         //    It also can be generated by the transport provider on the system (e.g. tcp)
  631.         //    when it decides that a connection is no longer in existance.
  632.         //
  633.         //    We receive the disconnect, but this program ignores the associated reason (NULL param).
  634.         //    It is possible to get back a kOTNoDisconnectErr from the OTRcvDisconnect call.
  635.         //    This can happen when either (1) the disconnect on the stream is hidden by a 
  636.         //    higher priority message, or (2) something has flushed or reset the disconnect
  637.         //    event in the meantime.   This is not fatal, and the appropriate thing to do is
  638.         //    to pretend the T_DISCONNECT event never happened.   Any other error is unexpected
  639.         //    and needs to be reported so we can fix it.  Next, unbind the endpoint so we can
  640.         //    reuse it for a new inbound connection.
  641.         //    
  642.         //    It is possible to get an error on the unbind due to a bug in OT 1.1.1 and earlier.
  643.         //    The best thing to do for that is close the endpoint and open a new one to replace it.
  644.         //    We do this back in the main thread so we don't have to deal with synchronization problems.
  645.         //
  646.         case T_DISCONNECT:
  647.         {
  648.             epState = OTGetEndpointState(epi->erf);
  649.             if (epState == T_OUTCON)
  650.             {
  651.                 OTAtomicAdd32(-1, &gCntrPending);
  652.             }
  653.             OTAtomicAdd32(1, &gCntrDiscon);
  654.             err = OTRcvDisconnect(epi->erf, NULL);
  655.             if (err != kOTNoError)
  656.             {
  657.                 if (err == kOTNoDisconnectErr)
  658.                     return;
  659.                 DBAlert1("Notifier: T_DISCONNECT - OTRcvDisconnect error %d", err);
  660.                 return;
  661.             }
  662.             
  663.             err = OTUnbind(epi->erf);
  664.             if (err != kOTNoError)
  665.             {
  666.                 OTLIFOEnqueue(gBrokenEPs, &epi->link);
  667.                 OTAtomicAdd32(1, &gCntrBrokenEPs);
  668.             }
  669.             return;
  670.         }
  671.         
  672.         //
  673.         //    T_GODATA:
  674.         //
  675.         //    Because of the complexity involved in the implementation of OT flow control,
  676.         //    it is sometimes possible to receive a T_GODATA even when we aren't subject 
  677.         //    to flow control - normally only at the start of a program.   If this happens,
  678.         //    ignoring it is the correct thing to do.
  679.         //
  680.         case T_GODATA:
  681.         {
  682.             return;
  683.         }
  684.         
  685.         //
  686.         //    T_OPENCOMPLETE:
  687.         //
  688.         //    This event occurs when an OTAsyncOpenEndpoint() completes.   Note that this event,
  689.         //    just like any other async call made from outside the notifier, can occur during
  690.         //    the call to OTAsyncOpenEndpoint().  That is, in the main thread the program did
  691.         //    the OTAsyncOpenEndpoint(), and the notifier is invoked before control is returned
  692.         //    to the line of code following the call to OTAsyncOpenEndpoint().   This is one
  693.         //    event we need to keep track of even if we are shutting down the program since there
  694.         //    is no way to cancel outstanding OTAsyncOpenEndpoint() calls.
  695.         //
  696.         case T_OPENCOMPLETE:
  697.         {
  698.             char serverCString[256];
  699.             
  700.             OTAtomicClearBit(&epi->stateFlags, kOpenInProgressBit);
  701.             if (result == kOTNoError)
  702.                 epi->erf = (EndpointRef) cookie;
  703.             else    
  704.             {
  705.                 DBAlert1("Notifier: T_OPENCOMPLETE result %d", result);
  706.                 return;
  707.             }
  708.  
  709.             if (gProgramState != kProgramRunning)
  710.                 return;
  711.             
  712.             if (epi == gDNS)
  713.             {
  714.                 P2CStr(gServerAddrStr, serverCString);
  715.                 err = OTInetStringToAddress((InetSvcRef)epi->erf, serverCString, &gServerHostInfo);
  716.                 if (err != kOTNoError)
  717.                 {
  718.                     //
  719.                     //    Can't translate the server address string
  720.                     //
  721.                     DBAlert1("Notifier: T_OPENCOMPLETE - OTInetStringToAddress error %d", err);
  722.                 }
  723.                 return;        // DNS resumes at T_DNRSTRINGTOADDRCOMPLETE
  724.             }
  725.             else
  726.             {
  727.                 OTAtomicAdd32(1, &gCntrEndpts);
  728.                 
  729.                 //
  730.                 //    Set to blocking mode so we don't have to deal with kEAGAIN errors.
  731.                 //    Async/blocking is the best mode to write an OpenTransport application in.
  732.                 //
  733.                 err = OTSetBlocking(epi->erf);
  734.                 if (err != kOTNoError)
  735.                 {
  736.                     DBAlert1("Notifier: T_OPENCOMPLETE - OTSetBlocking error %d", err);
  737.                     return;
  738.                 }
  739.                 
  740.                 DoBind(epi);
  741.                 return;            // resumes at T_BINDCOMPLETE
  742.             }
  743.         }
  744.         
  745.         //
  746.         //    T_ORDREL:
  747.         //
  748.         //    This event occurs when an orderly release has been received on the stream.
  749.         //
  750.         case T_ORDREL:
  751.         {
  752.             err = OTRcvOrderlyDisconnect(epi->erf);
  753.             if (err != kOTNoError)
  754.             {
  755.                 DBAlert1("Notifier: T_ORDREL - OTRcvOrderlyDisconnect error %d", err);
  756.                 return;
  757.             }
  758.             epState = OTGetEndpointState(epi->erf);
  759.             if (epState != T_IDLE)
  760.                 return;
  761.             OTAtomicAdd32(-1, &gCntrConnections);
  762.             err = OTUnbind(epi->erf);
  763.             if (err != kOTNoError)
  764.             {
  765.                 OTLIFOEnqueue(gBrokenEPs, &epi->link);
  766.                 OTAtomicAdd32(1, &gCntrBrokenEPs);
  767.             }
  768.             return;
  769.         }
  770.         
  771.         //
  772.         //    T_UNBINDCOMPLETE:
  773.         //
  774.         //    This event occurs on completion of an OTUnbind().
  775.         //    The endpoint is ready for reuse on a new inbound connection.
  776.         //    Put it back into the queue of idle endpoints.
  777.         //    Note that the OTLIFO structure has atomic queue and dequeue,
  778.         //    which can be helpful for synchronization protection.  
  779.         //
  780.         case T_UNBINDCOMPLETE:
  781.         {
  782.             if (result != kOTNoError)
  783.             {
  784.                 //
  785.                 //    Unbind errors can occur as a result of a bug in OT 1.1.1 and earlier
  786.                 //    versions.   The best recovery is to put the endpoint in the broken
  787.                 //    list for recycling with a clean, new endpoint.
  788.                 //
  789.                 OTLIFOEnqueue(gBrokenEPs, &epi->link);
  790.                 OTAtomicAdd32(1, &gCntrBrokenEPs);
  791.                 return;
  792.             }
  793.             DoBind(epi);
  794.             return;
  795.         }
  796.         
  797.         
  798.         //
  799.         //    default:
  800.         //
  801.         //    There are events which we don't handle, but we don't expect to see
  802.         //    any of them.   When running in debugging mode while developing a program,
  803.         //    we exit with an informational alert.   Later, in the production version
  804.         //    of the program, we ignore the event and try to keep running.
  805.         //
  806.         default:
  807.         {
  808.             DBAlert1("Notifier: unknown event <%x>", event);
  809.             return;
  810.         }
  811.     }
  812. }
  813.  
  814. //
  815. //    ReadData:
  816. //
  817. //    This routine attempts to read all available data from an endpoint.
  818. //    Since this routine is only called from inside the notifier in the current
  819. //    version of OTVirtualClient, it is not necessary to program to handle
  820. //    getting back a T_DATA notification DURING an OTRcv() call, as would be
  821. //    the case if we read from outside the notifier.   We must read until we
  822. //    get a kOTNoDataErr in order to clear the T_DATA event so we will get
  823. //    another notification of T_DATA in the future.
  824. //
  825. //    Currently this application uses no-copy receives to get data.  This obligates
  826. //    the program to return the buffers to OT asap.  Since this program does nothing
  827. //    with data other than count it, that's easy.  Future, more complex versions
  828. //    of this program will do more interesting things with regards to that.
  829. //
  830. static void ReadData(EPInfo* epi)
  831. {
  832.     OTBuffer* bp;
  833.     OTResult  res;
  834.     OTFlags      flags;
  835.     
  836.     while (true)
  837.     {
  838.         res = OTRcv(epi->erf, &bp, kOTNetbufDataIsOTBufferStar, &flags);
  839.         
  840.         if (res > 0)
  841.         {
  842.             OTAtomicAdd32(res, &gCntrBytesRcvd);
  843.             OTAtomicAdd32(res, &gCntrTotalBytesRcvd);
  844.             OTAtomicAdd32(res, &gCntrIntervalBytes);
  845.             OTReleaseBuffer(bp);
  846.             continue;
  847.         }
  848.         if (res == kOTNoDataErr)
  849.         {
  850.             //
  851.             //    Since ReadData is only called from inside the notifier
  852.             //    we don't have to worry about having missed a T_DATA 
  853.             //    during the OTRcv.
  854.             //
  855.             return;
  856.         }
  857.         if (res <= 0)
  858.         {
  859.             if (res == kOTLookErr)
  860.             {
  861.                 res = OTLook(epi->erf);
  862.                 if (res == T_ORDREL)
  863.                     return;
  864.                 if (res == T_GODATA)
  865.                 {
  866.                     //
  867.                     //    This isn't expected, but it has happened occasionally.
  868.                     //    The correct way to proceed is to ignore it.
  869.                     //
  870.                     continue; 
  871.                 }
  872.                 else
  873.                 {
  874.                     DBAlert1("ReadData: OTRcv got OTLookErr 0x%08x", res);
  875.                 }
  876.             }
  877.             else
  878.             {
  879.                 DBAlert1("ReadData: OTRcv error %d", res);
  880.             }
  881.         }
  882.     } 
  883. }
  884.  
  885. //
  886. //    Recycle:
  887. //
  888. //    This routine shouldn't be necessary, but it is helpful to work around both
  889. //    problems in OpenTransport and bugs in this program.   Basicly, whenever an
  890. //    unexpected error occurs which shouldn't be fatal to the program, the EPInfo
  891. //    is queued on the BrokenEP queue.  When recycle is called, once per pass around
  892. //    the event loop, it will attempt to close the associated endpoint and open
  893. //    a new one to replace it using the same EPInfo structure.   This process of
  894. //    closing an errant endpoint and opening a replacement is probably the most
  895. //    reliable way to make sure that this program and OpenTransport can recover
  896. //    from unexpected happenings in a clean manner.
  897. //
  898. static void Recycle()
  899. {
  900.     OTLink*     list = OTLIFOStealList(gBrokenEPs);
  901.     OTLink*        link;
  902.     EPInfo*        epi;
  903.  
  904.     while ( (link = list) != NULL )
  905.     {
  906.         list = link->fNext;
  907.         epi = OTGetLinkObject(link, EPInfo, link);
  908.         if (!EPClose(epi))
  909.         {
  910.             OTLIFOEnqueue(gBrokenEPs, &epi->link);
  911.             continue;
  912.         }
  913.         OTAtomicAdd32(-1, &gCntrBrokenEPs);
  914.         EPOpen(epi, OTCloneConfiguration(gCfgMaster));
  915.     }
  916. }
  917.  
  918. //
  919. //    SendRequest:
  920. //
  921. //    Tell the OT Virtual Server we want it to send us some data.
  922. //    For demonstration purposes, the server will wait for a 128 byte
  923. //    "request" to come in before sending us data.   It doesn't care
  924. //    what the request looks like, it just allows us to better simulate
  925. //    true client/server interactions.
  926. //
  927. static void SendRequest(EPInfo* epi)
  928. {
  929.     OTResult res;
  930.     
  931.     res = OTSnd(epi->erf, gServerRequest, kServerRequestSize, 0);
  932.     
  933.     //
  934.     //    This is bogus and needs to add flow control.
  935.     //    The only reason we get away with it here is because flow control
  936.     //    will never happen in the first 128 bytes sent, and that is all
  937.     //    we are sending.
  938.     //
  939.     if (res != kServerRequestSize)
  940.     {
  941.         DBAlert1("SendRequest: got result %d", res);
  942.     }
  943.     OTAtomicAdd32(res, &gCntrIntervalBytes);
  944. }
  945.  
  946.  
  947. //
  948. //    StartClient:
  949. //
  950. //    Open one InetServices (DNS) object,
  951. //    and as many connection endpoints as the program will use.
  952. //    Start making connections as soon as the server's name is translated
  953. //    to an IP address.
  954. //
  955. static void StartClient()
  956. {
  957.     int i;
  958.     EPInfo* epi;
  959.     OSStatus err;
  960.     
  961.     gCntrEndpts                = 0;
  962.     gCntrPending            = 0;
  963.     gCntrConnections        = 0;
  964.     gCntrBrokenEPs                = 0;
  965.     gCntrTotalConnections    = 0;
  966.     gIdleEPs->fHead            = NULL;
  967.     gBrokenEPs->fHead         = NULL;
  968.     gClientState             = kClientRunning;
  969.     TCPPrefsReset();
  970.     gWaitForServerAddr         = true;
  971.     
  972.     //
  973.     //    Open an InternetServices so we have access to the DNR
  974.     //    to translate the server's name into an IP address (if necessary).
  975.     //
  976.     gDNS = (EPInfo*) NewPtr(sizeof(EPInfo));
  977.     if (gDNS == NULL)
  978.     {
  979.         DBAlert("StartClient: NewPtr cannot get memory for EPInfo");
  980.         return;
  981.     }
  982.     OTMemzero(gDNS, sizeof(EPInfo));
  983.     OTAtomicSetBit(&gDNS->stateFlags, kOpenInProgressBit);
  984.     err = OTAsyncOpenInternetServices(kDefaultInternetServicesPath, 0, Notifier, gDNS);
  985.     if (err != kOTNoError)
  986.     {
  987.         OTAtomicClearBit(&gDNS->stateFlags, kOpenInProgressBit);
  988.         DBAlert1("OTAsyncOpenInternetServices error %d", err);
  989.         return;
  990.     }
  991.     
  992.     //
  993.     //    Get memory for EPInfo structures
  994.     //
  995.     for (i = 0; i < gMaxConnections; i++)                
  996.     {
  997.         epi = (EPInfo*) NewPtr(sizeof(EPInfo));
  998.         if (epi == NULL)
  999.         {
  1000.             DBAlert("StartClient: NewPtr cannot get memory for EPInfo");
  1001.             return;
  1002.         }
  1003.         OTMemzero(epi, sizeof(EPInfo));
  1004.         epi->next = gConnectors;
  1005.         gConnectors = epi;
  1006.     }
  1007.     
  1008.     //
  1009.     //    Open endpoints which can be used for outbound 
  1010.     //    connections to the server.
  1011.     //
  1012.     gCfgMaster = OTCreateConfiguration("tcp");
  1013.     if (gCfgMaster == NULL)
  1014.     {
  1015.         DBAlert("StartClient: OTCreateConfiguration returned NULL");
  1016.         return;
  1017.     }
  1018.     for (epi = gConnectors; epi != NULL; epi = epi->next)
  1019.     {
  1020.         if (!EPOpen(epi, OTCloneConfiguration(gCfgMaster)))
  1021.             break;
  1022.     }
  1023. }
  1024.  
  1025. //
  1026. //    StopClient:
  1027. //
  1028. //    This is where the client is shut down, either because the user clicked
  1029. //    the stop button, or because the program is exiting (error or quit).
  1030. //    The tricky part is that we can't quit while there are outstanding
  1031. //    OTAsyncOpenEndpoint calls (which can't be cancelled, by the way).
  1032. //
  1033. static void StopClient()
  1034. {
  1035.     EPInfo *epi, *last;
  1036.     
  1037.     gClientState = kClientShuttingDown;
  1038.     
  1039.     //
  1040.     //    First, make sure the DNS is closed.
  1041.     //
  1042.     if (gDNS != NULL)
  1043.     {
  1044.         if (!EPClose(gDNS))
  1045.             return;
  1046.         DisposPtr((char*)gDNS);
  1047.         gDNS = NULL;
  1048.     }
  1049.     
  1050.     //
  1051.     //    Start closing connector endpoints.
  1052.     //    While we could be rude and just close the endpoints, 
  1053.     //    we try to be polite and wait for all outstanding connections
  1054.     //    to finish before closing the endpoints.   The is a bit easier
  1055.     //    on the server which won't end up keeping around control blocks
  1056.     //    for dead connections which it doesn't know are dead.  Alternately,
  1057.     //    we could just send a disconnect, but this seems cleaner.
  1058.     //
  1059.     epi = gConnectors;
  1060.     last = NULL;
  1061.     while (epi != NULL)
  1062.     {
  1063.         if (!EPClose(epi))
  1064.         {
  1065.             //    Can't close this endpoint yet, so skip it.
  1066.             last = epi;
  1067.             epi = epi->next;
  1068.             continue;
  1069.         }
  1070.         else
  1071.         {
  1072.             if (last != NULL)
  1073.             {
  1074.                 last->next = epi->next;
  1075.                 DisposPtr((char*)epi);
  1076.                 epi = last->next;
  1077.             }
  1078.             else
  1079.             {
  1080.                 gConnectors = epi->next;
  1081.                 DisposPtr((char*)epi);
  1082.                 epi = gConnectors;
  1083.             }
  1084.         }
  1085.     }
  1086.     
  1087.     //
  1088.     //    If the list is empty now, then all endpoints have been successfully closed,
  1089.     //    so the client is stopped now.   At this point we can either restart it or
  1090.     //    exit the program safely.
  1091.     //
  1092.     if (gConnectors == NULL)
  1093.     {
  1094.         gClientState             = kClientStopped;
  1095.         gCntrEndpts                = 0;
  1096.         gCntrIdleEPs                = 0;
  1097.         gCntrPending            = 0;
  1098.         gCntrConnections        = 0;
  1099.         gCntrBrokenEPs                = 0;
  1100.         gCntrTotalConnections    = 0;
  1101.         gIdleEPs->fHead            = NULL;
  1102.         gBrokenEPs->fHead         = NULL;
  1103.         OTDestroyConfiguration(gCfgMaster);
  1104.     }
  1105. }
  1106.  
  1107. //
  1108. //    TimerInit
  1109. //
  1110. //    Start up a regular timer to do housekeeping.   Strictly speaking,
  1111. //    this isn't necessary, but having a regular heartbeat allows us to
  1112. //    detect if we are so busy with network notifier processing that the
  1113. //    program's event loop isn't ever firing.   We want to know this so
  1114. //    we can at least allow the user to quit the program if they want to.
  1115. //
  1116. static void TimerInit()
  1117. {
  1118.     gTimerTask = OTCreateTimerTask(&TimerRun, 0);
  1119.     if (gTimerTask == 0)
  1120.     {
  1121.         sprintf(gProgramErr, "TimerInit: OTCreateTimerTask returned 0");
  1122.         gProgramState = kProgramError;
  1123.         return;
  1124.     }
  1125.     OTScheduleTimerTask(gTimerTask, kTimerInterval);
  1126. }
  1127.  
  1128. //
  1129. //    TimerDestroy
  1130. //
  1131. static void TimerDestroy()
  1132. {
  1133.     if (gTimerTask != 0)
  1134.     {
  1135.         OTCancelTimerTask(gTimerTask);
  1136.         OTDestroyTimerTask(gTimerTask);
  1137.         gTimerTask = 0;
  1138.     }
  1139. }
  1140.  
  1141. //
  1142. //    TimerRun
  1143. //
  1144. //    Fires every N seconds, no matter how busy the system is.
  1145. //    We use this to detect if the program's main event loop is getting no time,
  1146. //    in which case we can slow the client down by doing a throttle-back until
  1147. //    the event loop can run at least once.  It also is a convenient statistics 
  1148. //    gathering point.  
  1149. //
  1150. static pascal void TimerRun(void*)
  1151. {
  1152.     gConnectsPerSecond = (gCntrIntervalConnects / kTimerIntervalInSeconds);
  1153.     gKBytesPerSecond = (gCntrIntervalBytes / (kTimerIntervalInSeconds * 1024));
  1154.     gEventsPerSecond = (gCntrIntervalEventLoop / kTimerIntervalInSeconds);
  1155.     if (gCntrIntervalEventLoop == 0)
  1156.         gWaitForEventLoop = true;
  1157.     
  1158.     if (gConnectsPerSecond > gConnectsPerSecondMax)
  1159.         gConnectsPerSecondMax = gConnectsPerSecond;
  1160.     if (gKBytesPerSecond > gKBytesPerSecondMax)
  1161.         gKBytesPerSecondMax = gKBytesPerSecond;
  1162.     if (gEventsPerSecond > gEventsPerSecondMax)
  1163.         gEventsPerSecondMax = gEventsPerSecond;
  1164.         
  1165.     gCntrIntervalConnects    = 0;
  1166.     gCntrIntervalBytes        = 0;
  1167.     gCntrIntervalEventLoop    = 0;
  1168.     gDoWindowUpdate            = true;
  1169.     gCntrConnections         = gCntrEndpts - gCntrPending - gCntrBrokenEPs;
  1170.     
  1171.     OTScheduleTimerTask(gTimerTask, kTimerInterval);
  1172. }
  1173.  
  1174.     
  1175.  
  1176. //////////////////////////////////////////////////////////////////////////////////////
  1177. //
  1178. //    Macintosh Program Wrapper
  1179. //
  1180. //    The code from here down deals with the Macintosh environment, events,
  1181. //    menus, command keys, etc.   Networking code is in the section above.
  1182. //    Since this code is fairly basic, and since this isn't really intended
  1183. //    to be a "sample Macintosh application" (just a sample OpenTransport application)
  1184. //    this section isn't heavily commented.   There are much better Macintosh
  1185. //    application samples for handling mouse, keyboard, event loops, etc.
  1186. //
  1187. //////////////////////////////////////////////////////////////////////////////////////
  1188.  
  1189. static void AboutBox()
  1190. {
  1191.     Alert(kAboutBoxResID, NULL);
  1192. }
  1193.  
  1194. static Boolean EventDialog(EventRecord* event)
  1195. {
  1196.     DialogPtr     dp;
  1197.     short        item;
  1198.     short        itemType;
  1199.     Handle        itemHandle;
  1200.     Rect        itemRect;
  1201.     
  1202.     if (event->modifiers & cmdKey)
  1203.     {
  1204.         EventKeyDown(event);        // this allows menu commands while dialog is active window
  1205.         return false;                // note if I add cut/paste I will have to rework this.
  1206.     }
  1207.     if ((DialogSelect(event, &dp, &item)) && (dp == gDialogPtr))
  1208.     {
  1209.         GetDItem(gDialogPtr, item, &itemType, &itemHandle, &itemRect);
  1210.         switch (item)
  1211.         {
  1212.             case kServerAddrDItem:
  1213.                 GetIText(itemHandle, gServerAddrStr);
  1214.                 return true;
  1215.             
  1216.             case kServerPortDItem:
  1217.                 GetIText(itemHandle, gServerPortStr);
  1218.                 return true;
  1219.             
  1220.             case kMaxConnectionsDItem:
  1221.                 GetIText(itemHandle, gMaxConnectionsStr);
  1222.                 return true;
  1223.             
  1224.             case kStartStopDItem:
  1225.                 GetDItem(gDialogPtr, kStartStopDItem, &itemType, &itemHandle, &itemRect);
  1226.                 if (gClientRunning)
  1227.                 {
  1228.                     StopClient();                
  1229.                     SetCTitle((ControlHandle)itemHandle, gStartStr);
  1230.                     gClientRunning = false;
  1231.                 }
  1232.                 else
  1233.                 {
  1234.                     StartClient();
  1235.                     SetCTitle((ControlHandle)itemHandle, gStopStr);
  1236.                     gClientRunning = true;
  1237.                 }
  1238.                 DrawDialog(gDialogPtr);
  1239.                 return true;
  1240.         }
  1241.     }
  1242.     return false;
  1243. }
  1244.  
  1245. static void TCPPrefsReset()
  1246. {
  1247.     StringToNum(gServerPortStr, &gServerPort);
  1248.     StringToNum(gMaxConnectionsStr, &gMaxConnections);
  1249. }
  1250.  
  1251. static void TCPPrefsDialog()
  1252. {
  1253.     short    itemType;
  1254.     Handle    itemHandle;
  1255.     Rect    itemRect;
  1256.     
  1257.     gDialogPtr = GetNewDialog(kTCPPrefsDlogResID, NULL, kInFront);
  1258.     SetWTitle(gDialogPtr, "\pTCP Preferences");
  1259.     
  1260.     GetDItem(gDialogPtr, kServerAddrDItem, &itemType, &itemHandle, &itemRect);
  1261.     SetIText(itemHandle, gServerAddrStr);
  1262.     
  1263.     GetDItem(gDialogPtr, kServerPortDItem, &itemType, &itemHandle, &itemRect);
  1264.     SetIText(itemHandle, gServerPortStr);
  1265.     
  1266.     GetDItem(gDialogPtr, kMaxConnectionsDItem, &itemType, &itemHandle, &itemRect);
  1267.     SetIText(itemHandle, gMaxConnectionsStr);
  1268.     
  1269.     GetDItem(gDialogPtr, kStartStopDItem, &itemType, &itemHandle, &itemRect);
  1270.     if (gClientRunning)
  1271.         SetCTitle((ControlHandle)itemHandle, gStopStr);
  1272.     else
  1273.         SetCTitle((ControlHandle)itemHandle, gStartStr);
  1274.     DrawDialog(gDialogPtr);
  1275. }
  1276.  
  1277. static void DialogClose()
  1278. {
  1279.     DisposDialog(gDialogPtr);
  1280.     gDialogPtr = NULL;
  1281.     TCPPrefsReset();
  1282. }
  1283.  
  1284. static void MenuDispatch(long menu)
  1285. {
  1286.     short menuID;
  1287.     short cmdID;
  1288.     
  1289.     menuID = HiWord(menu);
  1290.     cmdID  = LoWord(menu);
  1291.     switch(menuID)
  1292.     {
  1293.         case kAppleMenuResID:
  1294.         {
  1295.             switch (cmdID)
  1296.             {
  1297.                 case kAppleMenuAbout:
  1298.                     AboutBox();
  1299.                     break;
  1300.                     
  1301.                 default:
  1302.                     break;
  1303.             }
  1304.             break;
  1305.         }
  1306.             
  1307.         case kFileMenuResID:
  1308.         {
  1309.             switch (cmdID)
  1310.             {
  1311.                 case kFileMenuQuit:
  1312.                     gProgramState = kProgramDone;
  1313.                     break;
  1314.                     
  1315.                 case kFileMenuOpen:
  1316.                     WindowOpen();
  1317.                     break;
  1318.                     
  1319.                 case kFileMenuClose:
  1320.                     WindowClose();
  1321.                     break;
  1322.                     
  1323.                 default:
  1324.                     break;
  1325.             }
  1326.             break;
  1327.         }
  1328.         
  1329.         case kEditMenuResID:
  1330.             break;
  1331.         
  1332.         case kClientMenuResID:
  1333.         {
  1334.             switch (cmdID)
  1335.             {
  1336.                 case kClientMenuTCPPrefs:
  1337.                     TCPPrefsDialog();
  1338.                     break;
  1339.                     
  1340.                 default:
  1341.                     break;
  1342.             }
  1343.             break;
  1344.         }
  1345.     }
  1346.         
  1347. }
  1348.  
  1349. static void EventDrag(WindowPtr wp, Point loc)
  1350. {
  1351.     Rect dragBounds;
  1352.     
  1353.     dragBounds = qd.screenBits.bounds;
  1354.     DragWindow(wp, loc, &dragBounds);
  1355. }
  1356.  
  1357. static void EventGoAway(WindowPtr wp, Point loc)
  1358. {
  1359.     if (TrackGoAway(wp, loc))
  1360.     {
  1361.         if (wp == gWindowPtr)
  1362.             WindowClose();
  1363.         else if (wp == gDialogPtr)
  1364.             DialogClose();
  1365.     }
  1366. }
  1367.     
  1368. static void EventMouseDown(EventRecord* event)
  1369. {
  1370.     short        part;
  1371.     WindowPtr    wp;
  1372.     long         menu;
  1373.     
  1374.     part = FindWindow(event->where, &wp);
  1375.     switch (part)
  1376.     {
  1377.         case inMenuBar:
  1378.             menu = MenuSelect(event->where);
  1379.             HiliteMenu(0);
  1380.             MenuDispatch(menu);
  1381.             break;
  1382.             
  1383.         case inDrag:
  1384.             EventDrag(wp, event->where);
  1385.             break;
  1386.         
  1387.         case inGoAway:
  1388.             EventGoAway(wp, event->where);
  1389.             break;
  1390.         
  1391.         case inContent:        
  1392.             SelectWindow(wp);
  1393.             break;
  1394.             
  1395.         case inGrow:        // no grow box
  1396.         case inZoomIn:        // no zoom box
  1397.         case inZoomOut:        // no zoom box
  1398.         case inSysWindow:
  1399.         case inDesk:
  1400.         default:
  1401.             break;
  1402.     }
  1403. }
  1404.  
  1405. static void EventKeyDown(EventRecord* event)
  1406. {
  1407.     char        c;
  1408.     long        menu;
  1409.     
  1410.     c = event->message & charCodeMask;
  1411.     if (event->modifiers & cmdKey)
  1412.     {
  1413.         // cmd key
  1414.         menu = MenuKey(c);
  1415.         HiliteMenu(0);
  1416.         if (menu != 0)
  1417.             MenuDispatch(menu);
  1418.     }
  1419.     else
  1420.     {
  1421.         // normal keystroke
  1422.     }
  1423. }
  1424.  
  1425. static void EventLoop()
  1426. {
  1427.     EventRecord event;
  1428.     
  1429.     while ((gProgramState == kProgramRunning) || (gClientState != kClientStopped))
  1430.     {
  1431.         OTAtomicAdd32(1, &gCntrIntervalEventLoop);
  1432.         if (WaitNextEvent(everyEvent, &event, gSleepTicks, 0)) 
  1433.         {
  1434.             if ((gDialogPtr != NULL) && (IsDialogEvent(&event)))
  1435.             {
  1436.                 if (EventDialog(&event))
  1437.                     continue;
  1438.             }
  1439.             switch (event.what)
  1440.             {
  1441.                 case keyDown:
  1442.                     EventKeyDown(&event);
  1443.                     break;
  1444.                     
  1445.                 case mouseDown:
  1446.                     EventMouseDown(&event);
  1447.                     break;
  1448.                     
  1449.                 case updateEvt:
  1450.                     // redraw window now
  1451.                     break;
  1452.                 
  1453.                 case activateEvt:
  1454.                     // activate or deactivate window controls
  1455.                     break;
  1456.                 
  1457.                 case mouseUp:
  1458.                 case keyUp:
  1459.                 case autoKey:
  1460.                 case diskEvt:
  1461.                 case app4Evt:
  1462.                 default:
  1463.                     break;
  1464.             }
  1465.         }
  1466.         
  1467.         if (((gProgramState == kProgramRunning) && (gClientState == kClientShuttingDown)) ||
  1468.             ((gProgramState != kProgramRunning) && (gClientState != kClientStopped)))
  1469.             StopClient();
  1470.         else if ((gProgramState == kProgramRunning) && (gClientState == kClientRunning))
  1471.             NetEventLoop();
  1472.         WindowUpdate();
  1473.     }
  1474. }
  1475.  
  1476. static void WindowClose()
  1477. {
  1478.     if (gWindowPtr == NULL)
  1479.         return;
  1480.     DisposeWindow(gWindowPtr);
  1481.     gWindowPtr = NULL;
  1482. }
  1483.  
  1484. static void WindowOpen()
  1485. {
  1486.     if (gWindowPtr != NULL)
  1487.         return;
  1488.     gWindowPtr = GetNewWindow(kWindowResID, NULL, kInFront);
  1489.     SetWTitle(gWindowPtr, "\pOTVirtualClient");
  1490. }
  1491.  
  1492. static void WindowUpdate()
  1493. {
  1494.     char gStrBuf[128];
  1495.     int len;
  1496.     
  1497.     if (gWindowPtr == NULL)
  1498.         return;
  1499.  
  1500.     if (gDoWindowUpdate == false)
  1501.         return;
  1502.     gDoWindowUpdate = false;
  1503.         
  1504.     SetPort(gWindowPtr);
  1505.     EraseRgn(gWindowPtr->visRgn);
  1506.     
  1507.     gCntrConnections = gCntrEndpts - gCntrIdleEPs - gCntrPending - gCntrBrokenEPs;
  1508.     
  1509.     MoveTo(20, 20);
  1510.     sprintf(gStrBuf, "EPs: total %d idle %d", gCntrEndpts, gCntrIdleEPs);
  1511.     len = strlen(gStrBuf) ;
  1512.     DrawText(gStrBuf, 0, len);
  1513.     
  1514.     MoveTo(20, 40);
  1515.     sprintf(gStrBuf, "Connects: current %d total %d", gCntrConnections, gCntrTotalConnections);
  1516.     len = strlen(gStrBuf) ;
  1517.     DrawText(gStrBuf, 0, len);
  1518.  
  1519.     MoveTo(20, 60);
  1520.     sprintf(gStrBuf, "Pending connections %d", gCntrPending);
  1521.     len = strlen(gStrBuf) ;
  1522.     DrawText(gStrBuf, 0, len);
  1523.  
  1524.     MoveTo(20, 80);
  1525.     sprintf(gStrBuf, "KBytes received %d", (gCntrTotalBytesRcvd / 1024));
  1526.     len = strlen(gStrBuf) ;
  1527.     DrawText(gStrBuf, 0, len);
  1528.         
  1529.     MoveTo(20, 100);
  1530.     sprintf(gStrBuf, "Conn/sec: current %d max %d", gConnectsPerSecond, gConnectsPerSecondMax);
  1531.     len = strlen(gStrBuf) ;
  1532.     DrawText(gStrBuf, 0, len);
  1533.     
  1534.     MoveTo(20, 120);
  1535.     sprintf(gStrBuf, "KBy/sec: current %d max %d", gKBytesPerSecond, gKBytesPerSecondMax);
  1536.     len = strlen(gStrBuf) ;
  1537.     DrawText(gStrBuf, 0, len);
  1538.     
  1539.     MoveTo(20, 140);
  1540.     sprintf(gStrBuf, "Events/sec: %d/%d", gEventsPerSecond, gEventsPerSecondMax);
  1541.     len = strlen(gStrBuf) ;
  1542.     DrawText(gStrBuf, 0, len);
  1543.     
  1544.     MoveTo(20, 160);
  1545.     sprintf(gStrBuf, "Running at %d%% of capacity.", 
  1546.             (100 - ((100 * gEventsPerSecond)/gEventsPerSecondMax)));
  1547.     len = strlen(gStrBuf) ;
  1548.     DrawText(gStrBuf, 0, len);
  1549.     
  1550.     MoveTo(20, 180);
  1551.     sprintf(gStrBuf, "Disconnects %d", gCntrDiscon);
  1552.     len = strlen(gStrBuf) ;
  1553.     DrawText(gStrBuf, 0, len);
  1554.     
  1555. }
  1556.  
  1557.  
  1558. static void SetupMenus()
  1559. {
  1560.     MenuHandle mh;
  1561.     mh = GetMenu(kAppleMenuResID);
  1562.     AddResMenu( mh, 'DRVR' );            /* Add DA list */
  1563.     InsertMenu(mh, 0);
  1564.     mh = GetMenu(kFileMenuResID);
  1565.     InsertMenu(mh, 0);
  1566.     mh = GetMenu(kEditMenuResID);
  1567.     InsertMenu(mh, 0);
  1568.     mh = GetMenu(kClientMenuResID);
  1569.     InsertMenu(mh, 0);
  1570.     DrawMenuBar();
  1571. }
  1572.  
  1573. static void C2PStr(char* cstr, Str255 pstr)
  1574. {
  1575.     //
  1576.     //    Converts a C string to a Pascal string.
  1577.     //    Truncates the string if longer than 254 bytes.
  1578.     //
  1579.     int i, j;
  1580.     
  1581.     i = strlen(cstr);
  1582.     if (i > 254)
  1583.         i = 254;
  1584.     pstr[0] = i;
  1585.     for (j = 1; j <= i; j++)
  1586.         pstr[j] = cstr[j-1];
  1587. }
  1588.  
  1589. static void P2CStr(Str255 pstr, char* cstr)
  1590. {
  1591.     int i;
  1592.     
  1593.     for (i = 0; i < pstr[0]; i++)
  1594.         cstr[i] = pstr[i+1];
  1595.     cstr[i] = 0;
  1596. }
  1597.  
  1598. static void AlertExit(char* err)
  1599. {
  1600.     Str255 pErr;
  1601.     
  1602.     C2PStr(err, pErr);
  1603.     ParamText(pErr, NULL, NULL, NULL);
  1604.     Alert(kAlertExitResID, NULL);
  1605.     ExitToShell();    
  1606. }
  1607.  
  1608. static void MacInitROM()
  1609. {
  1610.     MaxApplZone();
  1611.     MoreMasters();
  1612.     InitGraf(&qd.thePort);
  1613.     InitCursor();
  1614.     InitFonts();
  1615.     InitWindows();
  1616.     InitMenus();
  1617.     TEInit();
  1618.     InitDialogs(NULL);
  1619.     FlushEvents(everyEvent, 0);
  1620. }
  1621.  
  1622. static void MacInit()
  1623. {
  1624.     MacInitROM();
  1625.     WindowOpen();
  1626.     SetupMenus();
  1627. }
  1628.  
  1629. static void MiscInit()
  1630. {
  1631.     int i;
  1632.     
  1633.     //    This is just so the data is a little better than random for tracing
  1634.     for (i = 0; i < kServerRequestSize; i++)
  1635.         gServerRequest[i] = i;
  1636. }
  1637.  
  1638. void main()
  1639. {
  1640.     MacInit();
  1641.     NetInit();
  1642.     MiscInit();
  1643.     EventLoop();
  1644.     NetShutdown();
  1645.     if (gProgramState == kProgramError)
  1646.         AlertExit(gProgramErr);
  1647. }
  1648.